Skip to content

S03-11 JS-高级-ES6-内置类

[TOC]

Symbol

API-Symbol

构造方法

  • Symbol()(description?)ES2015原始数据类型,用于创建唯一标识符作为属性键,避免对象属性名冲突。

静态属性

  • Symbol.iterator()=>Iterator,是 JS 中的内置 Symbol,它定义了对象的默认迭代器,是使对象可迭代的关键机制。

属性

  • sym.descriptionstring|undefinedES2020只读,用于获取创建 Symbol 时传入的可选描述字符串

静态方法

  • Symbol.for()(description),用于在全局 Symbol 注册表中搜索或创建 Symbol
  • Symbol.keyFor()(sym),用于检索全局 Symbol 注册表中 Symbol 关联键(描述字符串) 的方法。

相关方法

  • Object.getOwnPropertySymbols()(obj),用于获取对象自身的所有 Symbol 属性键的静态方法。Symbol 属性键在常规属性遍历(如 Object.keys()for...in)中不可见。

ES5 痛点

ES5 痛点属性名冲突

在 ES6 之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突

  1. 示例:添加新属性时覆盖原属性

    比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性。

    image-20250804114704810

  2. 示例:封装方法中覆盖原属性

    比如我们前面在讲 apply、call、bind 实现时,我们有给其中添加一个 fn 属性,那么如果它内部原来已经有了 fn 属性了呢?

    image-20250804115545207

  3. 示例:混入中覆盖原属性

    比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉。

基本使用

Symbol(符号)ES2015第七种原始数据类型,用于创建唯一标识符作为属性键,避免对象属性名冲突。

对象属性名类型:在 ES6 中,对象的属性名可以使用字符串,也可以使用 Symbol 值。


创建 Symbol

  1. 局部 Symbol

    js
    // 创建匿名 Symbol
    const anonymousSymbol = Symbol();
    
    // 创建带描述的 Symbol
    const idSymbol = Symbol("user_id");
  2. 全局 Symbol

    js
    const globalSym = Symbol.for('key')

应用场景

  1. 作为对象属性键

    js
    // 作为对象属性键
    const idSymbol = Symbol("user_id");
    
    const user = {
      name: "Alice",
      [idSymbol]: 12345 // 使用 Symbol 作为键
    };
    
    console.log(user[idSymbol]); // 12345

核心特性

  • 唯一性:每个 Symbol 值都是完全唯一的。

    js
    const sym1 = Symbol();
    const sym2 = Symbol();
    console.log(sym1 === sym2); // false
    
    // 相同描述仍产生不同 Symbol
    const sym3 = Symbol("desc");
    const sym4 = Symbol("desc");
    console.log(sym3 === sym4); // false
  • 不可变性:Symbol 一旦创建就不能被修改。

  • 非字符串属性键:可用作对象属性名,避免命名冲突。

  • descriptionES2020,可以在创建 Symbol 值的时候传入一个描述文本。

对象属性键

Symbol 作为对象属性键

我们通常会使用 Symbol 在对象中表示唯一的属性名,它有以下三种写法:

  1. 写法一:属性名赋值

    js
    const user = {}
    user[idSymbol] = 12345
  2. 写法二:Object.defienProperty()

    js
    Object.defineProperty(user, idSymbol, {
        value: 12345,
        enumerable: true,
        configurable: true,
        writable: true
    })
  3. 写法三:对象字面量

    js
    const user = {
      name: "tom",
      [idSymbol]: 12345 // 使用 Symbol 作为键
    };

遍历 Symbol 属性键

遍历Symbol属性键

Symbol 作为属性键,具有不可枚举性

js
const sym1 = Symbol("key1");
const sym2 = Symbol("key2");
const obj = {
  [sym1]: "value1",
  [sym2]: "value2",
  name: "Alice"
};

const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(key1), Symbol(key2)]

Symbol 分类

局部 Symbol

局部 Symbol:通过 Symbol() 创建。

js
const localSymbol = Symbol("local");

特性

  • 每次调用创建唯一值
  • 不会添加到全局注册表。
  • 最适合模块内部使用。

全局 Symbol

前面我们讲 Symbol 的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的 Symbol应该怎么来做呢?

答案是使用 Symbol.for()


全局 Symbol:通过 Symbol.for() 创建或获取。

js
// 创建或获取全局 Symbol
const globalSymbol1 = Symbol.for("app.id");
const globalSymbol2 = Symbol.for("app.id");

console.log(globalSymbol1 === globalSymbol2); // true

特性

  • 在全局 Symbol 注册表中存储

  • 跨领域共享(iframe、Web Workers 等)

  • 相同的 key,通过Symbol.for()可以生成相同的 Symbol 值

    js
    const s1 = Symbol.for("ss");
    const s2 = Symbol.for("ss");
    console.log(s1 === s2); // true
  • 通过Symbol.keyFor() 可以获取通过 Symbol.for()传入的 key

    js
    const s1 = Symbol.for("ss");
    const s3 = Symbol.for()
    
    console.log(Symbol.keyFor(s1)); // ss
    console.log(Symbol.keyFor(s3)); // undefined

Set

API-Set

构造方法

  • new Set()(iterable?),用于创建一个 Set 对象的构造函数。暂时没有字面量创建方式

属性

  • set.sizenumber只读,用于获取当前 Set 中元素的数量

方法

  • set.add()(value)修改原集合,用于向 Set 集合中添加新元素。遵循集合的唯一性原则,自动过滤重复值。
  • set.delete()(value)修改原集合,用于从集合中移除指定的元素
  • set.has()(value),用于高效检查指定值是否存在于集合中
  • set.clear()()修改原集合,用于一次性移除集合中的所有元素
  • set.forEach()(callbackFn, thisArg?),允许对集合中的每个元素遍历执行指定操作。

基本使用

ES5:在 ES6 之前,我们存储数据的结构主要有两种:数组对象

image-20250804141207317

ES6:在 ES6 中新增了另外两种数据结构:SetMap,以及它们的另外形式 WeakSetWeakMap


Set:是 ES6 引入的一种集合数据结构,它允许你存储任何类型的唯一值(包括原始类型和对象引用)。

对比数组:类似于数组,但是和数组的区别是元素不能重复


核心特性

  1. 唯一性保证:自动过滤重复值
  2. 值类型多样性:支持所有 JS 类型
  3. 插入顺序保留:元素按添加顺序排列
  4. 高效操作:添加、删除、查找均为 O(1) 时间复杂度

创建 Set

创建 Set 我们需要通过 Set 构造函数(暂时没有字面量创建的方式):

js
// 1. 空 Set
const emptySet = new Set();

// 2. 从数组初始化(自动去重)
const numberSet = new Set([1, 2, 2, 3]); // Set(3) {1, 2, 3}

// 3. 从字符串初始化
const charSet = new Set("hello"); // Set(4) {"h", "e", "l", "o"}

// 4. 混合类型
const mixedSet = new Set([42, "42", true, null, undefined, {}, NaN]);

唯一性规则

不同于数组,Set 集合中存储的元素都是不能重复。

  1. 可以匹配 NaN:可以正确识别 NaN(尽管 NaN !== NaN)。

    js
    const set = new Set();
    set.add(NaN);
    set.add(NaN); // 被忽略
    console.log(set.size); // 1
  2. 严格类型检查:不同类型但值相同的元素被区分。

    js
    set.add(5);
    set.add("5"); // 添加成功,5 和 "5" 视为不同值
    console.log(set.size); // 3
  3. 对象引用区分:只匹配相同内存地址的对象。

    js
    const obj1 = { id: 1 };
    const obj2 = { id: 1 }; // 内容相同但引用不同
    set.add(obj1);
    set.add(obj2); // 添加成功
    console.log(set.size); // 5

迭代操作

Set 的参数是可迭代对象,它支持以下迭代遍历操作:

  1. forEach 遍历

    js
    const techStack = new Set(["JavaScript", "React", "Node.js"]);
    
    techStack.forEach((value, key, set) => {
      console.log(value); // value 和 key 相同
    });
  2. for...of 循环

    js
    for (const tech of techStack) {
      console.log(tech);
    }
  3. 转换为数组

    js
    const techArray = [...techStack];

应用:数组去重

数组去重

方式一:利用Set特性实现

我们可以发现 Set 中存放的元素是不会重复的,那么 Set 有一个非常常用的功能就是给数组去重

js
// 4. 应用:数组去重
const arr = ["刘备", "关羽", "张飞", "吕布", "关羽", "刘备"];
const set2 = new Set(arr);
console.log(set2); // Set(4) {'刘备', '关羽', '张飞', '吕布'}
const set3 = Array.from(set2);
console.log(set3); // (4) ['刘备', '关羽', '张飞', '吕布']

// 简单写法一
console.log(Array.from(new Set(arr))); // (4) ['刘备', '关羽', '张飞', '吕布']
// 或者写法二
console.log([...new Set(arr)]); // (4) ['刘备', '关羽', '张飞', '吕布']

方式二:遍历数组,判断元素是否已存在

image-20250804142254031

判断 Set@

判断Set类型有以下2种方法:

方法一:instanceof

js
const s = new Set([1,2,3])
console.log(s instanceof Set) // true

方法二:Object.prototype.toString.call()

js
const s = new Set([1,2,3])
console.log(Object.prototype.toString.call(s)) // [object Set]

WeakSet

API-WeakSet

构造方法

  • new WeakSet()(iterable?),用于创建一个 WeakSet 对象的构造函数。暂时没有字面量创建方式

方法

  • weakSet.add()(value),用于向弱集合中添加对象引用
  • weakSet.delete()(value),用于从弱集合中移除特定对象的引用
  • weakSet.has()(value),用于确定特定对象是否存在于弱集合中

基本使用

WeakSet:是 ES6 引入的一种特殊的集合类型,它专门用于存储对象的弱引用,这意味着它们不会阻止垃圾回收机制回收这些对象。


核心特性

不同于 Set

  • 仅存储对象:只能添加对象引用(不能存储原始值)

    js
    new WeakSet().add("string"); // TypeError
  • 弱引用机制:如果没有其他引用对某个对象进行引用,那么 GC 可以对该对象进行回收,无法控制何时从 WeakSet 中移除对象。

    js
    let obj = { data: "important" };
    const weakSet = new WeakSet();
    weakSet.add(obj);
    
    // 解除对象引用
    obj = null; 
    
    // 垃圾回收后,对象自动从 WeakSet 中移除
    // 无法验证,但引擎会在适当时机回收
  • 不可迭代:没有遍历方法(如 forEach、keys、values)

    原理: WeakSet 只是对对象的弱引用,如果遍历获取到其中的元素,有可能会造成对象不能正常的销毁。

    js
    // 以下操作都会导致错误
    weakSet.forEach(() => {});
    console.log(weakSet.size);
    for (const item of weakSet) {}
  • 无 size 属性:无法获取元素数量

  • 自动清理:当对象被回收时自动从集合中移除

类似 Set:

  • 唯一性:内部元素不能重复

对比 Set

特性WeakSetSet
元素类型仅对象任意类型
引用强度弱引用强引用
垃圾回收影响对象可被回收对象不会被回收
可迭代性不可迭代✅ 可迭代
size属性❌ 无✅ 有
clear()方法❌ 无✅ 有
内存管理自动清理需手动管理
使用场景关联对象元数据、临时跟踪对象通用唯一值集合

创建 WeakSet

创建 WeakSet 我们需要通过 WeakSet 构造函数:

js
// 空 WeakSet
const weakSet = new WeakSet();

// 使用对象初始化
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const weakSet = new WeakSet([obj1, obj2]);

强引用/弱引用@

强引用

强引用普通对象的内存图

image-20250806104808611

说明

  1. 普通对象被重新赋值为 null 时,就断开了和内存中对象的联系。
  2. 但是由于之前已经将对象的内存地址赋值给了数组 arr,赋值为 null 后这些对象依然被数组 arr 所引用,所以它们并不会被销毁

弱引用

弱引用WeakSet 内存图

image-20250806105909872

说明: 添加到 WeakSet 中的对象都是弱引用,可能会被 GC 随时回收。

应用场景

WeakSet 应用:限制类中方法的调用者

事实上这个问题并不好回答,我们来使用一个 Stack Overflow 上的答案;

需求:默认情况下,类中的running()方法有多种方式可以调用,我们希望可以限制它的这种任意性:只有通过类的实例对象才能调用类中的方法。

image-20250804145858763

思路

  1. 每次new一个实例时,将该实例保存到WeakSet中。

  2. 之后调用类中方式时,判断是否是通过保存的实例调用。

注意:此处用 WeakSet 的好处

  • 使用 WeakSet 想要销毁实例对象 p 的时候,可以直接通过p = null 销毁。
  • 如果使用 Set 的话,由于实例对象一直被 Set 引用,所以无法销毁。

image-20250804150829882

Map

API-Map

构造方法

  • new Map()(iterable?),用于创建一个新的 Map 对象的构造函数。暂时没有字面量创建方式

方法

  • map.set()(key, value)修改原集合,用于添加或更新键值对
  • map.get()(key),用于根据键获取对应的值
  • map.has()(key),用于检查指定键是否存在于 Map 中
  • map.delete()(key)修改原集合,用于从 Map 中删除指定的键值对
  • map.clear()()修改原集合,用于完全清空 Map 中的所有键值对。
  • map.forEach()(callbackFn, thisArg?),用于按插入顺序遍历 Map 的所有键值对并执行回调函数。

基本使用

对象类型的局限性

之前我们一直使用对象来存储映射关系,对象存储有什么局限呢:

  • 对象存储映射关系只能用字符串或 Symbol(ES6新增) 作为属性名。

  • 某些情况下我们可能希望使用其他类型(如对象)作为属性名,这个时候会自动将对象转成字符串来作为key,这并不能达成我们的需求。

  • 此时我们就可以使用Map:

image-20250804154154939


Map:是 JS 于 ES6 引入的一种用于存储键值对(key-value pairs,映射关系)的集合数据结构。


核心特性

  1. 任意类型的键

    • 对象的键:只能是 StringSymbol
    • Map 的键:可以是任意类型:函数、对象、数字、布尔值、NaN,甚至另一个 Map
    js
    const map = new Map();
    map.set({ id: 1 }, "对象作为键"); // ✅ 对象作为键
    map.set(42, "数字作为键");      // ✅ 数字作为键
    map.set(() => {}, "函数作为键"); // ✅ 函数作为键
  2. Map中不能存储包含相同键的元素,可以存储包含相同值得元素。

    js
      const m4 = new Map([
        [1, "tom"],
        [1, "jack"], // 覆盖前项
        ["name", "jerry"],
        ["nick", "jerry"], // OK
      ]);
      console.log("m4: ", m4); // => {1 => 'jack', 'name' => 'jerry', 'nick' => 'jerry'}
  3. 遍历顺序:保持插入顺序

    • 遍历 Map 时:元素严格按插入顺序返回
    • 变量普通对象时:属性顺序不保证稳定(尤其在数字键上)。
  4. 高性能的增删查改

    对频繁增删键值对的场景进行了优化,性能优于普通对象。


对比 普通对象

特性MapObject
键的类型任意值(函数、对象等)仅 String / Symbol
顺序保证插入顺序不保证顺序(尤其数字键)
大小获取map.size 直接获取需手动计算 Object.keys()
默认键无默认键(纯净)有原型链上的继承属性
性能高频增删场景更优静态键值对场景更优
序列化不能直接 JSON 序列化支持 JSON 序列化

创建 Map

创建 Map 我们需要通过 Map 构造函数(暂时没有字面量创建的方式):

js
// 1. 空 Map
const emptyMap = new Map()

// 2. 从数组初始化(二维数组)
const map = new Map([
  [{name: 'tom'}, 18],
  [{name: 'jack'}, 30]
])

WeakMap

API-WeakMap

构造方法

  • new WeakMap()(iterable?),用于创建一个 WeakMap 对象的构造函数。暂时没有字面量创建方式

方法

  • weakMap.set()(key, value)修改原集合,用于添加或更新键值对,主要围绕弱引用机制设计。
  • weakMap.get()(key),用于根据键对象获取关联的值,其行为受 弱引用机制 约束。
  • weakMap.has()(key),用于检查指定的键对象是否存在于 WeakMap 中,其行为受 弱引用机制 约束。
  • weakMap.delete()(key)修改原集合,用于从集合中移除指定的键值对

基本使用

WeakMap:是 ES6 引入的一种特殊的键值对集合,与 Map 类似但有一些关键区别,主要围绕弱引用机制设计。


核心特性

不同于 Map

  1. 键必须是对象

    WeakMap 的键只能是对象(包括数组、函数等引用类型),不能使用原始值(字符串、数字等)作为键:

    js
    const weakMap = new WeakMap();
    const objKey = { id: 1 };
    
    weakMap.set(objKey, "关联数据"); // ✅ 有效
    weakMap.set("stringKey", "值"); // ❌ TypeError: Invalid value used as weak map key
  2. 弱引用机制

    • 键是弱引用:WeakMap 对键的引用是"弱"的
    • 不影响垃圾回收:当键对象没有其他引用时,会被垃圾回收器回收
    • 自动移除条目:键被回收后,对应的键值对会自动从 WeakMap 中移除
    js
    let user = { name: "Alice" };
    const weakMap = new WeakMap();
    weakMap.set(user, "敏感数据");
    
    user = null; // 移除对对象的唯一引用
    
    // 垃圾回收后,user 对象及关联数据会被自动清除
  3. 不可枚举性

    WeakMap 没有遍历能力

    • 没有 keys()values()entries() 方法
    • 没有 forEach() 方法
    • 没有 size 属性
    js
    console.log(weakMap.size); // undefined
    weakMap.forEach(...); // TypeError: weakMap.forEach is not a function

WeakMap 的局限性

  1. 无法清空:没有 clear() 方法
  2. 无法查看内容:调试时无法检查内部数据
  3. 依赖垃圾回收:清除时机不确定
  4. 不支持原始值键:只能用对象作为键

对比 Map

特性WeakMapMap
键类型仅对象任意类型
垃圾回收键无引用时自动回收阻止键被回收
可枚举性不可枚举可枚举(keys/values/entries)
大小查询无 size 属性有 size 属性
遍历能力无遍历方法有 forEach 等方法
内存管理自动内存管理需手动删除避免内存泄漏
使用场景元数据/私有数据/缓存通用键值存储

创建 WeakMap

创建 WeakSet 我们需要通过 WeakSet 构造函数,暂时没有字面量创建方式:

js
// 空 WeakMap
const emptyWM = new WeakMap();

// 从数组初始化(二维数组)
const key1 = { name: 'key1' };
const key2 = [1, 2, 3];
const weakMap = new WeakMap([
  [key1, '值1'],
  [key2, { data: '值2' }]
]);

// 从Map初始化
const sourceMap = new Map([
  [document.body, '页面主体'],
  [new Date(), '创建时间']
]);
const domWM = new WeakMap(sourceMap);

应用场景

WeakMap 应用:实现响应式

WeakMap 有什么作用呢?(后续专门讲解)

image-20230620141751908

FinalizationRegistry

API-FinalizationRegistry

构造方法

  • new FinalizationRegistry()(cleanupCallback)ES2021,允许开发者注册一个回调函数,当对象被垃圾回收时自动执行清理操作

方法

  • registry.register()(target, heldValue, unregisterToken?)ES2021,用于注册需要垃圾回收监视的对象
  • registry.unregister()(unregisterToken)ES2021,用于取消之前通过 register() 方法设置的垃圾回收监听。

基本使用

FinalizationRegistryES2021,允许开发者注册一个回调函数,当对象被垃圾回收时自动执行清理操作。它在管理外部资源(如文件句柄网络连接等)时非常有用。

  1. 垃圾回收关联

    当一个对象被注册到 FinalizationRegistry 后,当该对象被垃圾回收器回收时,注册的回调函数会被触发(具体时机由 JS 引擎决定)。

  2. 资源清理

    主要用途是释放对象关联的外部资源(如内存、文件描述符等),这些资源无法被 JS 的垃圾回收器自动管理。


注意事项

  1. 执行时机不确定

    回调函数可能在对象回收后很久才执行,甚至可能永不执行(如页面快速关闭时)。不应依赖它处理关键逻辑

  2. 避免引用目标对象

    回调中不能直接引用被回收的对象(它已不存在)。应通过 heldValue 传递必要信息。

  3. 性能影响

    过度使用可能影响垃圾回收效率,仅在必要时使用。

  4. 优先显式清理

    对于重要资源(如数据库连接),永远优先使用显式清理方法(如 .close()),FinalizationRegistry 仅作为最后保障。


使用方法

  1. 创建注册表

    new FinalizationRegistry()(cleanupCallback)ES2021,允许开发者注册一个回调函数,当对象被垃圾回收时自动执行清理操作

    js
    const registry = new FinalizationRegistry(heldValue => {
      console.log(`清理资源: ${heldValue}`);
    })
  2. 注册对象

    registry.register()(target, heldValue, unregisterToken?)ES2021,用于注册需要垃圾回收监视的对象

    js
    const obj = { /* 某个对象 */ };
    const externalResource = { id: "file#123" }; // 关联的外部资源
    
    // 注册对象
    registry.register(obj, "资源标识", externalResource);
    // 或(推荐):将 externalResource 作为 heldValue
    registry.register(obj, externalResource);
  3. 取消注册(可选)

    registry.unregister()(unregisterToken)ES2021,用于取消之前通过 register() 方法设置的垃圾回收监听。

    js
    registry.unregister(externalResource); // 传入注册时的 heldValue/token
  4. 完整示例

    js
    class FileWrapper {
      constructor(filename) {
        this.resource = { fd: openFile(filename) }; // 模拟外部资源
          
        this.registry = new FinalizationRegistry(fd => {
          closeFile(fd); // 垃圾回收时自动关闭文件
        });
          
        this.registry.register(this, this.resource.fd, this.resource);
      }
    
      // 显式清理(推荐优先使用)
      close() {
        closeFile(this.resource.fd);
        this.registry.unregister(this.resource); // 取消注册
      }
    }
    
    // 使用
    const file = new FileWrapper("test.txt");
    // 当 file 对象被垃圾回收时,自动触发 closeFile

示例对象被删除时触发注册的回调

image-20250806230421595

WeakRef

API-WeakRef

构造方法

  • new WeakRef()(targetObject)ES2021,允许创建不会阻止垃圾回收的对象弱引用

方法

  • weakRef.deref()()ES2021,用于访问弱引用指向的目标对象

强引用/弱引用

强引用:如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用。

image-20250807101853582

弱引用:如果我们希望是一个弱引用的话,可以使用WeakRef

image-20250807102417296

强引用的问题

  1. 易造成内存泄露:当要删除一个对象时,需要删除它的所有引用才能彻底删除。

基本使用

WeakRefES2021,允许开发者创建对对象的弱引用不会阻止垃圾回收器回收该对象。


创建 WeakRef

new WeakRef()(targetObject)ES2021,允许创建不会阻止垃圾回收的对象弱引用

js
const obj = { data: "important" };
const weakRef = new WeakRef(obj);

访问目标对象:获取弱引用对象中的属性值需要通过 ref.deref() 取。

weakRef.deref()()ES2021,用于访问弱引用指向的目标对象

js
// 获取引用对象(如果尚未被回收)
const target = weakRef.deref();

if (target) {
  console.log(target.data); // "important"
} else {
  console.log("对象已被回收");
}

核心特性

  1. 弱引用:不会阻止垃圾回收器回收该对象。

  2. 只能引用对象:参数不能是原始类型

    js
    // ✅ 参数只能是对象
    const obj = {}
    const weakRef = new WeakRef(obj) // OK
    
    // ❌ 错误!不能是原始类型
    const invalidRef = new WeakRef(42); // TypeError
    const invalidRef2 = new WeakRef("text"); // TypeError
  3. 不可预测的回收时机

    js
    const ref = new WeakRef({});
    
    // 移除强引用
    obj = null;
    
    // 不可靠!
    setTimeout(() => {
      if (!ref.deref()) {
        console.log("已回收"); // 可能永远不会执行
      }
    }, 1000);

示例

  1. 结合 FinalizationRegistry 使用

    image-20250807103835091

Proxy@

API-Proxy

Proxy

构造方法

  • new Proxy()(target, handler)创建对象的代理(拦截器),用于拦截和自定义对象的底层操作。

handler

方法

监听普通对象

  • handler.set()(target, property, value, receiver),用于拦截对象属性赋值操作:objProxy.xxx=value
  • handler.get()(target, property, receiver?),用于拦截属性读取操作:objProxy.xxx
  • handler.has()(target, property),用于拦截属性存在性检查 in 操作:in
  • handler.defineProperty()(target,property,descriptor),用于拦截对象的属性定义操作:defineProperty()
  • handler.deleteProperty()(target, property),用于拦截对象的属性删除delete 操作:delete

监听函数对象

监听对象属性

需求:监听对象属性的访问/修改

有一个对象,我们希望监听这个对象中的属性被设置或获取的过程。

实现思路:遍历对象的所有属性,对每个属性使用 Object.defineProperty()存储属性描述符监听。

image-20250807174636376

上述实现思路的缺点:

  1. Object.defineProperty() 的设计初衷不是为了监听对象中的所有属性

    初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。

  2. 只能监听属性的设置和获取

    如果我们想监听更加丰富的操作(如新增、删除属性等)Object.defineProperty()是无能为力的。

  3. Proxy/Reflect 就是为了解决上述问题而出现的

基本使用

Proxy:是 ES6 引入的一种高级元编程特性,它允许你创建一个对象的代理(拦截器),用于拦截并自定义该对象的基本操作(如属性访问、赋值、函数调用等)

核心概念

  1. 目标对象(Target):被代理的原始对象。
  2. 处理器对象(Handler):定义拦截操作的函数集合(称为“陷阱”或“trap”)。
  3. 代理对象(Proxy):通过 new Proxy(target, handler) 创建的代理实例,所有对它的操作会被 handler 拦截。

语法

  • new Proxy()(target, handler)创建对象的代理(拦截器),用于拦截和自定义对象的底层操作。

示例:监听对象属性的访问/修改(Proxy版)

我们可以将上面的案例用Proxy来实现一次:

  1. 我们需要new Proxy对象,并且传入需要侦听的对象以及一个处理对象,可以称之为handler;

  2. 我们之后的操作都是直接对Proxy的操作,而不是原有的对象,因为我们需要在handler里面进行侦听;

image-20250807180533915

捕获器

所有捕获器

捕获器(Trap):是定义在处理器对象(handler)中的特殊方法,用于拦截目标对象的基本操作

如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕获器


13个捕获器分别是做什么的呢?

监听普通对象

  • handler.set()(target, property, value, receiver),用于拦截对象属性赋值操作:objProxy.xxx=value
  • handler.get()(target, property, receiver?),用于拦截属性读取操作:objProxy.xxx
  • handler.has()(target, property),用于拦截属性存在性检查 in 操作:in
  • handler.defineProperty()(target,property,descriptor),用于拦截对象的属性定义操作:defineProperty()
  • handler.deleteProperty()(target, property),用于拦截对象的属性删除delete 操作:delete

监听函数对象


示例:

image-20230803110138914

监听普通对象

set()/get()
  • handler.set()(target, property, value, receiver),用于拦截对象属性赋值操作:objProxy.xxx=value
  • handler.get()(target, property, receiver?),用于拦截属性读取操作:objProxy.xxx

示例:set()/get()

image-20250808103111521

deleteProperty()

示例:deleteProperty()

image-20250808102940659

has()
  • handler.has()(target, property),用于拦截属性存在性检查 in 操作:in

示例:has()

image-20250808103416987

监听函数对象

当然,我们还会看到捕捉器中还有construct和apply,它们是应用于监听函数对象的:

construct()
  • handler.construct()(target, args, newTarget),用于拦截构造函数的 new 操作new FnProxy()

示例:construct()

image-20250808104550489

apply()
  • handler.apply()(target, thisArg, args),用于拦截函数调用的操作:fnProxy.apply()

示例:apply()

image-20250808104335800

Reflect@

API-Reflect

Reflect中有哪些常见的方法呢?它和Proxy是一一对应的,也是13个

  • 监听普通对象
  • Reflect.get(target, prop, receiver?)obj.name,获取属性值
  • Reflect.set(target, prop, newValue, receiver?)obj.name = 'mr',设置属性值
  • Reflect.has(target, prop)in, 判断是否存在某属性
  • Reflect.defineProperty(target, prop, descriptor)Object.defineProperty, 设置属性描述符
  • Reflect.deleteProperty(target, prop)delete,删除属性
  • 监听函数对象
  • Reflect.apply(target, thisArg, args)Object.prototype.apply,函数调用
  • Reflect.construct(target, args, newTarget?)new,调用构造函数
  • Reflect.getPrototypeOf(target)Object.getPrototypeOf, 获取对象的原型
  • Reflect.setPrototypeOf(target, prototype)Object.setPrototypeOf, 设置对象的原型
  • Reflect.isExtensible(target)Object.isExtensible, 判断是否可以新增属性
  • Reflect.preventExtensions(target)Object.preventExtensions, 阻止对象扩展
  • Reflect.ownKeys(target)Object.getOwnPropertyNamesObject.getOwnPropertySymbols ,获取自身上的所有属性
  • Reflect.getOwnPropertyDescriptor(target, prop)Object.getOwnPropertyDescriptor, 获取自身上的属性描述符

基本使用

Reflect:是 JS 的一个内置对象,它提供了一组用于执行对象基本操作的静态方法。这些方法与 Proxy 对象的 handler 方法一一对应,用于实现对象的基本操作(如属性访问、定义、删除等)。


作用

  1. 提供了一组统一、函数式的对象操作方法
  2. 与 Proxy 完美配合,与 Proxy handler 一一对应,实现强大的拦截和自定义行为
  3. 为对象操作提供了更合理、更安全的替代方案
  4. 使 JS 的反射式编程能力标准化

Reflect 出现原因将 Object 中用于操作对象的一类方法抽取到 Reflect 中,实现更加规范的设计

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

  • 这是因为在早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面;

  • 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;

  • 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;

  • 所以在ES6中新增了Reflect,让我们将这些操作都集中到了Reflect对象上

  • 另外在使用Proxy时,可以做到不操作原对象


对比 Object 方法

  1. 返回值不同

    以下方法中Object返回原对象,而Reflect返回操作是否成功:

    • defineProperty()
    • preventExtensions()
    • setPrototypeOf()
  2. 目标对象是否支持原始类型

    以下方法中Object会自动包装原始类型,而Reflect会直接报TypeError错误:

    • getOwnPropertyDescriptor()
    • getPrototypeOf()
    • isExtensible()

示例:对比删除属性

  • Object的方式:delete obj.name

    缺点

    1. 无法直接返回是否删除成功,需要通过重新访问属性判断是否删除成功。
    2. 严格模式下,删除不可配置属性会报错

    image-20250808111123536

  • Reflect的方式:Reflect.deleteProperty()

    image-20250808111643136

结合Proxy使用

Reflect结合Proxy使用

结合使用思路:可以将之前Proxy案例中对原对象的操作,都修改为Reflect来操作

image-20250808120930184


Reflect结合Proxy使用的好处

单独使用Proxy的问题

  1. 缺点一:会直接操作原对象:这就失去了代理的意义。
  2. 缺点二:无法判断操作是否成功
  3. 缺点三:无法接收Proxy的 receiver

image-20250808121122022

Reflect结合Proxy的好处

  1. 好处1:可以不用再直接操作原对象

  2. 好处2:可以通过返回值判断是否操作成功

  3. 好处3:可以接收receiver修改setter/getter中的this指向

    receiver 就是外层的Proxy对象。

    Reflect.set/get 的最后一个参数 receiver 可以决定对象访问器 settter/getterthis 指向。

image-20250808121511721


接收receiver修改setter/getter中的this指向:实现 setter/getter 中的2次设置都能被 Proxy 监听到。

原理分析

  1. 默认情况下,对象 obj 的 setter/getter 中的 this 是指向原始对象 obj 的。

    image-20250808121813543

  2. 在 setter/getter 中其实设置了2次 obj 的属性

    1. 第一次通过 set name 设置属性值
    2. 第2次通过 this._name 设置属性值

    但通过 objProxy 设置/访问 obj 的属性时,只会触发一次 set/get 的监听方法(this._name没有监听到)

    • 这是因为this._name设置时,它的this指向的是obj原始对象,没有经过 objProxy 代理,所以无法监听到

    image-20250808122556849

  3. Reflect中接收的参数 receiver 可以修改 setter/getter 中的this指向,让它指向 objProxy。

    从而实现 setter/getter 中的2次设置都能被Proxy监听到

    image-20250808123335576

Reflect.construct()

应用:实现借用构造函数:通过执行父类中的代码创建一个子类的对象实例。

image-20250808125151015

上述代码也可以写在外部

image-20250808125228069


源码:Babel转class继承为ES5写法时有用到

image-20250808125351007

Promise

API-Promise

构造方法

  • new Promise()(executor)ES2015,用于创建一个 Promise 的实例对象

方法

  • promise.then()(onFulfilled?, onRejected?)ES2015,用于接收当状态变成 fulfilledrejected 时执行的回调函数。
  • promise.catch()(onRejected)ES2015,是 Promise 链式调用中的错误处理方法,用于捕获 Promise 链中发生的任何错误或拒绝。
  • promise.finally()(onFinally)ES2018,用于指定一个无论 Promise 最终状态如何(fulfilled 或 rejected)都会执行的回调函数。

静态方法

  • Promise.resolve()(value)ES2015,用于快速创建一个已解决(fulfilled)的 Promise 对象。提供了一种将值转换为 Promise 的标准方式。
  • Promise.reject()(reason)ES2015,用于立即创建一个已拒绝(rejected)状态的 Promise 对象。它提供了一种快速失败机制,在异步流程中直接抛出错误或拒绝信号。
  • Promise.all()(iterable)ES2015,用于并行处理多个 Promise,当所有 Promise 都成功时返回成功结果,当任意一个 Promise 失败时立即返回失败。
  • Promise.allsettled()(iterable)ES2020,用于等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的最终状态和结果。不会因某个 Promise 失败而提前终止。
  • Promise.race()(iterable)ES2015,用于获取第一个完成的 Promise 结果(无论成功或失败)。
  • Promise.any()(iterable)ES2021,用于获取第一个成功的 Promise 结果。当所有 Promise 都失败时,它会返回一个包含所有错误信息的 AggregateError。
  • Promise.try()(executor)ES2025,用于统一处理 Promise 链中的同步错误和异步错误
  • Promise.withResolvers()()ES2024

ES5异步处理方案

ES5中异步任务处理方案:回调函数

思路分析

  1. 异步任务指的是那些并不是立即执行的函数,execCode()执行完后需要等待3秒才会返回执行结果。

  2. 此时如果想知道该结果,需要通过在执行函数时传递的回调函数:

    当异步任务执行完毕后,会分以下情况:

    1. 如果任务执行成功,会调用传递的成功回调函数,并将执行结果作为参数传递出去。
    2. 如果任务执行失败,会调用传递的失败回调函数,并将失败原因作为参数传递出去。

image-20250808150439481


回调函数方案的缺点

  • 缺点1:定义困难

    需要自己设计回调函数:包括回调函数、回调函数的名称、回调函数的使用等。

  • 缺点2:使用困难

    对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用。

  • 缺点3:容易出现回调地狱

  • 针对以上困境,在ES6引入了Promise。

基本使用

Promise:是处理异步操作的核心机制,它代表一个尚未完成但预期将来会完成的操作(如网络请求、文件读取等)。

核心概念

  1. 三种状态(单向不可逆):
    • Pending(待定):初始状态,操作尚未完成。
    • Fulfilled(已兑现):操作成功完成(通过 resolve() 触发)。
    • Rejected(已拒绝):操作失败(通过 reject() 触发)。
  2. 结果不可变: 状态一旦变为 FulfilledRejected,结果将永久固定(不可修改或再次改变)。

基本使用

一、创建 Promise

思路分析

  1. 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据。就可以创建一个Promise的对象;

  2. 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为 executor

    这个回调函数会被立即执行,并且给它传入另外两个回调函数:resolvereject

    • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
    • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

image-20250808153828961


二、使用 Promise

通过 .then().catch().finally() 处理结果:

  • 方式一:普通写法

    image-20250808153957755

  • 方式二:链式调用

    image-20250808154133660

核心概念

三种状态

三种状态(单向不可逆)

上面Promise使用过程,我们可以将它划分成三种状态:

  • pending(待定):初始状态,操作尚未完成(执行 executor 中代码时的状态)。
  • fulfilled(已兑现):操作成功完成(通过 resolve() 触发)。
  • rejected(已拒绝):操作失败(通过 reject() 触发)。
  • 单向不可逆:状态一旦确定不能更改。只有第一次调用生效,后续调用无效。
  • 查看方式:可通过 console.dir(promise) 查看

image-20250808160320410


通过Promise重构之前的异步请求

那么有了Promise,我们就可以将之前的代码进行重构了:

image-20230620144006320

executor

executor:是在创建Promise时需要传入的一个处理业务逻辑的回调函数,这个回调函数会被立即执行,并且传入两个回调函数作为参数:

js
new Promise(
    // executor 回调函数
    (resolve, reject) => {
        console.log('executor 代码')
    }
)

核心特性

  1. 立即执行executor 在 Promise 创建时立即同步执行

  2. 状态转换

    1. executor 中执行 resolve() 会转为 fulfilled状态
    2. executor 中执行 reject() 会转为 rejected状态
  3. 错误抛出:在 executor 中抛出或出现错误等同于调用 reject()

    js
    new Promise(() => {
      throw new Error('出错了!'); // 等同于 reject
      nonExistentFunction(); // 自动触发 reject
    }).catch(err => console.log(err)); // 捕获错误

确定 Promise 状态

  1. 通常我们会在executor中确定Promise状态

    • 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);

    • 通过reject,可以拒绝(rejected)Promise的状态;

  2. 注意:一旦状态被确定下来,Promise的状态会被锁死(不可逆)

    • 在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成兑现(fulfilled);
    • 之后再去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);

resolve参数值类型

resolve() 参数值类型的处理:

  1. 情况1:普通值(包括原始类型、对象、数组等)

    当前 Promise 状态变成 fulfilled,并且该值会作为 then 回调的参数

    image-20250808162122859

  2. 情况2:另一个 Promise

    当前 Promise 的状态会"跟随"该新 Promise 的状态

    image-20250808162324045

  3. 情况3:thenable 对象(实现了 then() 方法的对象)

    会自动执行该 then 方法,并且根据 then 方法的结果来决定当前 Promise 的状态

    image-20250808162327466

实例方法

then()

参数

promise.then()(onFulfilled?, onRejected?)ES2015,用于接收当状态变成 fulfilledrejected 时执行的回调函数。

  • onFulfilled?res=>void,当 Promise 解决(fulfilled)时调用的函数

  • onRejected?err=>void,当 Promise 拒绝(rejected)时调用的函数

  • 返回:

  • promisePromise,返回一个新的 Promise 对象。其状态取决于回调函数的返回值类型。


示例:promise.then()

image-20250814172703099

多次调用

一个 promsie 对象的 then 方法可以被多次调用,每次调用都可以传入对应的 fulfilled 回调,当 Promise 状态变成 fulfilled时这些回调都会被执行。

js
const promise = new Promise((resolve, reject) => {
    resolve()
    // reject()
})

promise.then(res => console.log('第一次调用', res))
promise.then(res => console.log('第二次调用', res))
promise.then(res => console.log('第三次调用', res))

image-20250808172324588

返回值@
  1. then 方法的返回值会被 new Promise() 包裹

    • 即使返回一个普通的非 Promise类型值,也会被转化为 Promise。

    • Promise 的状态是等到then方法传入的回调函数有返回值时决定的。

    【示例】

  2. 链式调用

    then 方法支持链式调用,因为它会返回一个 Promise

    image-20250808172343931


then 方法返回的 Promise 的状态

Promise有三种状态,那么这个Promise处于什么状态呢?

  1. 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;

  2. 当then方法中的回调函数返回一个结果时

    • 情况1:普通值(包括原始类型、对象、数组等)

      当前 Promise 状态变成 fulfilled,并且该值会作为 resolve 的参数

      image-20250808174434788

    • 情况2:另一个 Promise

      当前 Promise 的状态会"跟随"该新 Promise 的状态,并将新 Promise 的结果作为当前 Promise 的结果

      image-20230803171156653

    • 情况3:thenable 对象(实现了 then() 方法的对象)

      会自动执行该 then 方法,并且根据 then 方法的结果来决定当前 Promise 的状态,并将 thenable 中的结果作为当前 Promise 的结果

      image-20230803171446486

  3. 当then方法抛出一个异常时,那么它处于reject状态;

    image-20250808174503842

catch()

多次调用

promise.catch()(onRejected)ES2015,是 Promise 链式调用中的错误处理方法,用于捕获 Promise 链中发生的任何错误或拒绝。

  • onRejectederr=>any,当 Promise 被拒绝(rejected)时调用的函数。可以返回

    • 普通值:会使新 Promise 以该值解决(fulfilled)。

    • Promise:新 Promise 将跟随该 Promise 的状态。

    • 抛出错误:新 Promise 将以该错误拒绝(rejected)。

  • 返回:

  • promisePromise,返回一个新的 Promise 对象,其状态取决于 onRejected 函数的行为:


多次调用

一个 promsie 对象的 catch方法可以被多次调用,每次调用都可以传入对应的 rejected 回调,当 Promise 状态变成 rejected 时这些回调都会被执行。

js
const promise = new Promise((resolve, reject) => {
    // resolve()
    reject()
})

promise.catch(err => console.log('第一次捕获错误:', err))
promise.catch(err => console.log('第二次捕获错误:', err))
promise.catch(err => console.log('第三次捕获错误:', err))
返回值

事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:

特性:

▸ catch方法也会返回一个新的promise对象

可以通过该promise对象继续调用then()、catch()方法

image-20241104102043410


▸ catch方法的执行时机:

catch方法中的回调函数会被最早的promise的reject()方法回调

image-20241104103210886


▸ catch、then方法返回的默认状态都是fulfilled,后续继续执行then方法

下面的代码中,promise.catch()后续是catch中的err2打印,还是then中的res打印呢?

答案是res打印,这是因为catch传入的回调在执行完后,默认状态依然会是fulfilled的;

image-20241104103646274


▸ 如果希望catch、then方法后续执行catch()方法,那么需要通过抛出一个异常修改返回状态为reject

抛出异常的方法:throw new Error('error message')

抛出异常后Promise内部会执行:new Promise(reject => reject(new Error()))

image-20250808180207457


▸ 如果抛出的异常后没有catch()方法捕获处理抛出的异常,就会报错

image-20241104105010644

image-20241104105013275


补充: 中断函数继续执行的方法:

  • 方法一:return
  • 方法二:throw new Error()
  • 方法三:yield(暂时中断函数执行)

finally()

promise.finally()(onFinally)ES2018,用于指定一个无论 Promise 最终状态如何(fulfilled 或 rejected)都会执行的回调函数。

  • onFinally()=>any,回调不接受参数,当 Promise 敲定(settled)时调用的函数(无论成功或失败)。

  • 返回:

  • promisePromise,返回一个新的 Promise 对象。


示例:finally() 基本使用

image-20250808215553516

类方法

resolve()

Promise.resolve()(value)ES2015,用于快速创建一个已解决(fulfilled)的 Promise 对象。提供了一种将值转换为 Promise 的标准方式。

  • valueany,可以是任何类型的值,它决定了返回的 Promise 状态。

  • 返回:

  • promisePromise,返回一个 Promise 对象。


前面我们学习的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise.prototype上的。

下面我们再来学习一下Promise的类方法。

特性:

Promise.resolve的用法相当于new Promise,并且执行resolve操作

image-20250808220339806

resolve参数的类型

  • 情况一:参数是一个普通的值或者对象

  • 情况二:参数本身是Promise

  • 情况三:参数是一个thenable


使用场景:

▸有时候我们已经有一个现成的内容,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。

image-20241104112126598

reject()

Promise.reject()(reason)ES2015,用于立即创建一个已拒绝(rejected)状态的 Promise 对象。它提供了一种快速失败机制,在异步流程中直接抛出错误或拒绝信号。

  • reasonany|Error,拒绝原因,可以是任意类型(通常为 Error 对象或错误描述)

  • 返回:

  • promisePromise,始终返回一个已拒绝状态(rejected)的 Promise 对象


特性:

▸ Promise.reject的用法相当于new Promise,只是会调用reject:

image-20250808220831377


▸ Promise.reject传入的参数无论是什么形态,都会直接作为rejected状态的参数传递到catch的。

image-20241104112653972

all()

Promise.all()(iterable)ES2015,用于并行处理多个 Promise,当所有 Promise 都成功时返回成功结果,当任意一个 Promise 失败时立即返回失败。

  • iterableIterator,可迭代对象(如 Array、Set),包含多个 Promise 或其他值。

  • 返回:

  • promisePromise,返回一个新的 Promise 对象。


特性:

▸ Promise.all()的作用是将多个Promise包裹在一起形成一个新的Promise

image-20241104114759789


▸ 新的Promise状态由包裹的所有promise共同决定,所有promise执行逻辑与运算

  • 所有的Promise状态都变成fulfilled状态时,新Promise状态为fulfilled,并将所有promise的返回值组成一个数组;

    image-20241104114338074

    image-20241104114131164

  • 有一个Promise状态为reject时,新Promise状态为reject,并将第一个reject的返回值作为参数;

    image-20241104114546052

    image-20241104114549524

应用场景: 发送网络请求时,当同时发送多个网络请求后,想等所有请求都有结果再一起返回,可以使用Promise.all

allSettled()

Promise.allsettled()(iterable)ES2020,用于等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的最终状态和结果。不会因某个 Promise 失败而提前终止。

  • iterableIterator,可迭代对象(如 Array、Set),包含多个 Promise 或其他值。

  • 返回:

  • promisePromise,始终返回一个已兑现状态(fulfilled)的 Promise 对象。


all方法的缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。

  • 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;

特性:

▸ Promise.allSettled()方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态;并且结果一定是fulfilled状态

image-20241104115655074


▸ 结果分析

  • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;

  • 这个对象中包含status状态,以及对应的value值;

  • 注意:reject结果是reason值

image-20230803181529408

race()

Promise.race()(iterable)ES2015,用于获取第一个完成的 Promise 结果(无论成功或失败)。

  • iterableIterator,可迭代对象(如 Array、Set),包含多个 Promise 或其他值。

  • 返回:

  • promisePromise,返回一个新的 Promise 对象。


特性

Promise.race()方法的所有promise谁先有结果,那么就使用谁的结果,无论结果是fulfilled还是rejected

image-20241104120253941

any()

Promise.any()(iterable)ES2021,用于获取第一个成功的 Promise 结果。当所有 Promise 都失败时,它会返回一个包含所有错误信息的 AggregateError。

  • iterableIterator,可迭代对象(如 Array、Set),包含多个 Promise 或其他值。

  • 返回:

  • promisePromise,返回一个新的 Promise 对象。


特性

▸ any()方法会等到一个fulfilled状态,才会决定新Promise的状态;如果所有的Promise都是reject,那么也会等到所有的Promise都变成rejected状态

image-20241104121343128


▸ any()方法执行逻辑或运算,如果所有的Promise都是reject的,会报AggregateError错误

image-20241104121738763

image-20241104121742381

手写-Promise@

Iterator@

API-Iterator

iterator({next(), return()?}),JS中的迭代器,实现了next方法的对象,在next方法中必须返回一个包含了done和value属性的对象

  • next()(arg?),返回一个包含 valuedone 属性的对象。

    • arg?any,可选参数。
    • 返回: {done, value}
    • doneboolean,是否已经遍历完所有元素。
      • false:表示迭代未完成,value值为any;
      • true:表示迭代完毕,value值为undefined
    • valueany | undefined,表示当前元素的值。
  • return()?(value),结束当前的迭代,并返回一个包含 done 属性的对象。

    • valueany,是 return() 方法返回的值,可以是任何类型的值。通常用来返回一个终止值,标识迭代的结束。
    • 返回: {done, value?}
    • doneboolean,表示迭代是否完成。调用 return() 时,这个属性的值通常是 true
    • value?any,返回迭代结束时的值。可以用来返回终止时需要返回的值。
  • js
    const customIterator = {
      items: [1, 2, 3, 4],
      index: 0,
      
      next() {
        if (this.index < this.items.length) {
          return { value: this.items[this.index++], done: false };
        } else {
        	return { value: undefined, done: true };  
        }
      },
    
      return(value) {
        console.log("Iteration is being stopped early!");
        return { value: value, done: true };
      }
    };
    
    const iter = customIterator;
    
    console.log(iter.next()); // { value: 1, done: false }
    console.log(iter.next()); // { value: 2, done: false }
    
    console.log(iter.return(99)); // Iteration is being stopped early! { value: 99, done: true }

概念:

  • 迭代器:Iterator。它是一种提供遍历集合元素的机制。
  • 可迭代对象:Iterable。它是一种实现了Iterator Protocol方法的对象。
  • 迭代器协议:Iterator Protocol。它是一种定义迭代器对象的标准,它规定了迭代器对象必须实现的方法和属性。

迭代器

迭代器协议

迭代器协议(Iterator Protocol):是 JS 中定义迭代器对象的标准规则,它规定了迭代器对象如何产生值序列。在 JS 中这个标准就是一个特定的 next() 方法。

迭代器协议-核心规则

一个对象要成为迭代器,必须实现一个 next() 方法,该方法返回一个包含两个属性的对象:

js
interface IteratorResult {
  value: any;    // 当前迭代的值(可选)
  done: boolean; // 迭代是否完成
}

基本要求

  1. next() 方法:无参数或接受一个参数
  2. 返回值:包含 valuedone 属性的对象
  3. 迭代结束done: true 时,value 可省略或为 undefined
  4. 后续调用:迭代结束后继续调用 next() 应返回 { done: true }

迭代器

迭代器(Iterator):提供了一种标准化的方式来遍历各种数据结构。迭代器协议定义了如何按顺序访问集合中的元素,使得不同的数据结构(如数组、Map、Set 等)可以使用统一的遍历机制。

  • 行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;

  • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;

在 JS 中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议


JS中的迭代器:是一个实现了迭代器协议的对象,它必须包含一个 next() 方法。每次调用 next() 方法都会返回一个包含两个属性的对象:

  • value:当前迭代的值。
  • done:布尔值,表示迭代是否完成。
js
const arr = ['a', 'b', 'c']

let index = 0
const iterator = {
    next: function() {
        if(index < arr.length) {
            return {done: false, vlaue: arr[index++]}
        } else {
            return {done: true, value: undefined}
        }
    }
}

示例:基本使用

专门针对 friends 数组的迭代器

image-20230620144529302


封装:通用的迭代器生成函数

更加通用,所有的数组都可以使用

image-20230620144536523

可迭代对象

可迭代协议

可迭代协议(Iterable Protocol):定义了对象如何被遍历的标准方式。通过实现可迭代协议,对象可以与 JS 的迭代语法(如 for...of 循环、扩展运算符等)无缝集成,提供统一的遍历接口。

可迭代协议-核心规则:一个对象要成为可迭代对象,必须实现 [Symbol.iterator]() (规范中叫 @@iterator())方法,该方法返回一个迭代器对象(iterator)。迭代器对象必须遵循迭代器协议,即实现 next() 方法。

js
const iterable = {
  // 实现可迭代协议
  [Symbol.iterator]() {
    // 返回迭代器对象(遵循迭代器协议)
    return {
      next() {
        // 返回 { value: any, done: boolean }
      }
    };
  }
};

可迭代协议的要素

  • [Symbol.iterator]() 方法
    • 是对象的特殊方法(使用 Symbol.iterator 作为键)
    • 必须返回一个迭代器对象
    • 不接受参数(用于自定义迭代行为)
  • 迭代器对象
    • 必须实现 next() 方法
    • 可选实现 return()throw() 方法(用于资源清理和错误处理)
  • next() 方法
    • 返回包含两个属性的对象:
      • value: 当前迭代的值
      • done: 布尔值,表示迭代是否完成
    • done: true 时,value 可省略

可迭代对象

上面的代码整体来说看起来是有点奇怪的:

  • 我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;

  • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;


可迭代对象(Iterable)实现了可迭代协议,即 [Symbol.iterator]() 方法的对象。该方法返回一个迭代器对象(iterator),该迭代器对象必须包含一个 next() 方法,用于访问数据结构中的元素。可迭代对象可以进行迭代操作(如 for...of 循环)。

迭代操作的本质:就是调用可迭代对象的 @@iterator() 方法。

js
const iterable = {
  [Symbol.iterator]() {
    // 返回迭代器对象
    return {
      next() {
        // 返回 { value: any, done: boolean }
      }
    };
  }
};

核心特性

  1. 可迭代对象内部需要实现 [Symbol.iterator]()

    该方法返回一个迭代器对象(iterator),该迭代器对象必须包含一个 next() 方法。

    image-20250816103146587

    image-20250816103234438

  2. 可迭代对象可以进行迭代操作

    迭代操作包括:for...of 遍历

    image-20250816103357815

优化

1、通用优化:绑定 this

next函数用箭头函数书写,可以让其内部的this指向可迭代对象infos,从而实现更通用的封装

image-20250816103629459

2、通用优化:迭代对象中的键/值/键值对

image-20250816103735389

image-20250816103803471

内置可迭代对象

事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个可迭代对象:

  • StringArrayMapSetarguments对象、NodeList集合;

用法

▸ 数组: arr[Symbol.iterator]()

image-20250816104251472


▸ Set: set[Symbol.iterator]()

image-20250816104336498


▸ arguments: arguments[Symbol.iterator]()

image-20250816104402467

应用场景

可迭代对象应用场景

那么可迭代对象可以被应用在哪里呢?

  • JS中语法

    • for...of遍历

    • ...展开运算符

    • yield*(后面讲)

    • [name, age] = arr解构赋值(Destructuring Assignment)

    image-20250811125328819

  • 创建一些对象时,可传入可迭代对象

    • new Map()(iterable?),用于创建一个新的 Map 对象的构造函数。暂时没有字面量创建方式
    • new WeakMap()(iterable?),用于创建一个 WeakMap 对象的构造函数。暂时没有字面量创建方式
    • new Set()(iterable?),用于创建一个 Set 对象的构造函数。暂时没有字面量创建方式
    • new WeakSet()(iterable?),用于创建一个 WeakSet 对象的构造函数。暂时没有字面量创建方式
  • 一些方法的调用

    • Promise.all()(iterable)ES2015,用于并行处理多个 Promise,当所有 Promise 都成功时返回成功结果,当任意一个 Promise 失败时立即返回失败。
    • Promise.race()(iterable)ES2015,用于获取第一个完成的 Promise 结果(无论成功或失败)。
    • Array.from()(arrayLike,mapFn?,thisArg?)静态方法,用于将类数组对象可迭代对象转换为真正的数组,并支持对元素进行映射处理。

    image-20250816105159603

自定义类的迭代

在前面我们看到Array、Set、String、Map等类创建出来的对象都是可迭代对象。

在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象。

如果我们也希望自定义类创建出来的对象默认是可迭代对象,那么在设计类的时候我们就可以添加上 @@iterator() 方法


案例需求:创建一个classroom的类

  • 教室中有自己的位置、名称、当前教室的学生;

  • 这个教室可以进来新学生(push);

  • 创建的教室对象是可迭代对象;


自定义类的迭代实现: 自定义类创建出来的对象默认是可迭代对象

实现思路:在设计类时添加 [Symbol.iterator]() 方法。

image-20250816105713147

image-20250816105729294

迭代器-监听中断

迭代器在某些情况下会在没有完全迭代的情况下中断:

  • 遍历的过程中:通过 breakreturnthrow 中断了循环操作;

  • 解构的时候:没有解构所有的值

那么这个时候我们想要监听中断的话,可以添加 return()throw() 方法:


示例:监听遍历中断

1、在迭代器函数中添加 return() 方法

image-20250816110352097

2、当在遍历中使用 breakreturnthrow 语句时,调用 return()throw() 方法

image-20250816110637047

Generator@

概念

生成器(Generator):是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

  • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

生成器函数:也是一个函数,但是和普通的函数有一些区别:

  • 首先,生成器函数需要在function的后面加一个符号:*function* foo() 或者function *foo()

  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程

  • 最后,生成器函数执行时返回一个Generator(生成器):

    • 要想执行函数内部的代码,需要通过生成器对象调用它的next方法
    • 当遇到yield时,就会中断执行,需要再次调用next方法,才会继续执行
  • 生成器本质上是一种特殊的迭代器

    • MDN:Instead, they return a special type of iterator, called a Generator.

生成器函数

我们发现下面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象

  • 那么我们如何可以让它执行函数中的东西呢?调用next即可;

  • 我们之前学习迭代器时,知道迭代器的next是会有返回值的;

  • 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果

image-20230620144819474

语法

生成器函数:是 JS 中的一种特殊函数类型,它通过 function* 语法定义,具有多次返回值的能力,可以暂停和恢复执行。

语法

js
function* generatorFunction() {
  // 生成器逻辑
}
  • function*:生成器函数使用 function* 来定义。可以在执行过程中暂停,并且可以多次返回值。
  • 生成器函数默认在执行时,返回一个生成器对象。
  • yield:表示生成器函数在此暂停,并返回一个值。直到外部调用 next() 方法。
  • next():生成器函数返回的是一个生成器对象,该对象具有 next() 方法。调用 next() 方法时,生成器从上次暂停的地方继续执行,直到遇到下一个 yield 或结束。
  • return():可以提前终止生成器并返回一个值。如果在 yield 后调用 return(),生成器将不再返回任何值,done 会变为 true
  • yield*:是一个委托表达式,用于将生成器的执行委托给另一个生成器。它会一次性执行另一个生成器,返回它的所有值。

示例:基本使用

js
// 1. 定义一个生成器函数
function* foo() {
    console.log('1111')
    console.log('2222')
    yield
    console.log('3333')
    console.log('4444')
    yield
    console.log('5555')
    console.log('6666')
}

// 2. 调用生成器函数,返回一个生成器对象
const gen = foo();

// 3. 调用生成器对象的next方法
console.log(gen.next());  // 1111, 2222
console.log(gen.next());  // 3333, 4444
console.log(gen.next());  // 5555, 6666

生成器函数的执行流程

  1. function 后面会跟上符号*

  2. 代码的执行可以被yeild控制

  3. 生成器函数默认子在执行时,返回一个生成器对象

    1. 要想执行函数内部的代码,需要生成器对象调用它的next()方法

    2. 当遇到yeild时,就会中断执行

返回值 yield

通过yield返回结果

image-20241111150434975

传递参数 next()

函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

  • 答案是可以的;

  • 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值

  • 注意:也就是说我们是为本次的函数代码块执行提供了一个值

传递参数规则:

  • 第一个yield之前代码块中的参数name1是通过foo('next1')函数传递参数,并通过foo(name1)接收参数
  • 第二个yield之前代码块中的参数name2是在调用第二个next('next2')时传递参数,并通过const name2 = yield 'aaaa'中的name2接收参数
  • 之后的yield代码块中的参数按照第二个yield的参数传递规则依次传递。
  • 注意: 第一次调用next()时,不传递参数

image-20241111152253261

提前结束 return()

还有一个可以给生成器函数传递参数的方法是通过return函数

return传值后这个生成器函数就会立即结束,之后调用next不会继续生成值了;

image-20241111154038082

抛出异常 throw()

除了给生成器函数内部传递参数之外,也可以向生成器函数内部抛出异常

1、通过 generator.throw(new Error('error message')) 向生成器函数内部抛出异常

image-20241111154733928

2、在生成器函数内部捕获异常

注意: 在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;

image-20241111154950247

应用场景

生成器替代迭代器

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器

▸ 利用生成器对之前的迭代器代码进行重构

1、之前的迭代器

image-20241111160212693

2、利用生成器重构之前的迭代器

image-20241111160303334


▸ 生成器函数,可以生成某个范围的值

image-20241111160701158

yield*

yield* 是一个委托表达式,用于将生成器的执行委托给另一个生成器可迭代对象。它会一次性执行另一个生成器,返回它的所有值。

语法:

js
yield* iterable
  • iterable:可以是任何具有迭代协议的对象,通常是另一个生成器、数组、字符串、Set、Map 等。

特性:

  • yield*yield 语句的一种语法糖。它会依次迭代这个可迭代对象,每次迭代其中的一个值。

  • yield* 只能存在于生成器函数中

  • yield* 语法非常适合处理递归生成器将多个生成器组合成一个生成器

用法:

▸ 基本用法:委托给另一个生成器

js
function* inner() {
  yield 1;
  yield 2;
}

function* outer() {
  yield* inner();  // 委托给 inner 生成器
  yield 3;
}

const gen = outer();
console.log(gen.next());  // { value: 1, done: false }
console.log(gen.next());  // { value: 2, done: false }
console.log(gen.next());  // { value: 3, done: false }
console.log(gen.next());  // { value: undefined, done: true }

▸ 一行代码重构迭代器:委托给数组

image-20241111161222048


▸ 使用 yield* 处理递归

js
// flatten 是一个递归生成器函数,使用 yield* 将内部的数组元素“平铺”到外部生成器中。
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flatten(item);  // 递归地展开数组
    } else {
      yield item;
    }
  }
}

const gen = flatten([1, [2, [3, 4], 5], 6]);

console.log(gen.next());  // { value: 1, done: false }
console.log(gen.next());  // { value: 2, done: false }
console.log(gen.next());  // { value: 3, done: false }
console.log(gen.next());  // { value: 4, done: false }
console.log(gen.next());  // { value: 5, done: false }
console.log(gen.next());  // { value: 6, done: false }
console.log(gen.next());  // { value: undefined, done: true }

▸ 重构自定义类的迭代

在之前的 自定义类的迭代 中,我们也可以换成生成器:

注意: 此处*[Symbol.iterator]()表示生成器函数,yield*只能存在于生成器函数中。

image-20241111163245241

对生成器的操作

既然生成器是一个迭代器,那么我们可以对其进行如下的操作:

image-20230620144954281

异步处理@

学完了我们前面的Promise、生成器等,我们目前来看一下异步代码的最终处理方案。

异步处理方案:

  • 方案一:回调嵌套,会造成回调地狱
  • 方案二:Promise链式调用
  • 方案三:Generator方案
  • 方案四:async await方案(终极方案

案例需求:

  • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;

  • 第二次的请求url依赖于第一次的结果;

  • 第三次的请求url依赖于第二次的结果;

  • 依次类推;

image-20241111173335351


▸ 方案一:回调嵌套,会造成回调地狱

image-20241111174007470


▸ 方案二:Promise链式调用

image-20241111174823793


▸ 方案三:Generator方案

▸ 方案四:async await方案(终极方案

Generator方案

但是上面的代码其实看起来也是阅读性比较差的,有没有办法可以继续来对上面的代码进行优化呢?

image-20241111180048846

自动化执行generator函数@

目前我们的写法有两个问题

  • 第一,我们不能确定到底需要调用几层的Promise关系;

  • 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?

所以,我们可以封装一个工具函数execGenerator自动执行生成器函数

image-20241113141944888

async/await@

异步函数 async function

async关键字用于声明一个异步函数

  • async是asynchronous单词的缩写,异步、非同步;

  • sync是synchronous单词的缩写,同步、同时;

写法: async异步函数可以有很多中写法

image-20230809181802296

image-20250811172225463


核心特性

  1. 异步函数默认情况下和普通函数一样

    image-20230809181618309

  2. 异步函数返回的是一个Promise

    image-20230810170218398

异步函数返回值

异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行

异步函数有返回值时,和普通函数会有区别

  • 情况一:异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到Promise.resolve中

  • 情况二:如果我们的异步函数的返回值是Promise,状态由会由Promise决定;

  • 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;


▸ 示例:返回一个普通的值

返回的普通值会被包裹到 Promise.resolve(value) 中。

image-20230810170654019


▸ 示例:返回一个 Promise

异步函数的状态由新返回的Promise决定。

image-20230810170916861


▸ 示例:返回一个 thenable 对象

then方法中也可以有timeout()延迟方法

image-20230810171039568

异步函数rejected

什么情况下异步函数的结果是rejected?

  • 情况一:在异步函数中返回一个执行reject的Promise

  • 情况二:如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;

▸ 情况一:在异步函数中返回一个执行reject的Promise

image-20230810171640577


▸ 情况二:在异步函数中抛出了异常

image-20241113151347558

image-20230810172151632

await关键字

async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。

image-20241113151921083

特点:

  • 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise

  • await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数

表达式返回值类型:

  • 如果await后面是一个普通的值,那么会直接返回这个值;

  • 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;

  • 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值;


▸ 基本用法:

image-20241113153016042


▸ await处理异步请求

第二次请求会等待第一次请求的结果

image-20230810175240345


▸ 在异步函数中抛出异常的处理方式

image-20241113153753925

1、方式一:在异步函数后的catch中捕获异常

image-20241113153934160

2、方式二:在异步函数内部,通过try catch,捕获异常

image-20230810180056033


await和async结合使用

await后面既可以跟随返回Promise的普通函数,也可以跟随一个异步函数

image-20241113155654367

image-20241113155637351